// 拷贝一份数组的原型
const arrayPrototype=Array.prototype;
// 以 Array.prototype 为原型创建 arrayMethods 对象
const arrayMethods=Object.create(arrayPrototype);

// 需要改写的数组方法列表
const methodsNeedChange=[
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse',
]

for(let i=0;i<methodsNeedChange.length;i++){
  // 备份原来的方法
  const original=arrayMethods[methodsNeedChange[i]];
  // 定义新的方法
  def(arrayMethods,methodsNeedChange[i],function () {
    // 用来保存新插入的值
    let inserted=[];
    // 由于 arguments 对象是类数组，所以先通过扩展运算符转为数组之后，再进行操作。
    let args=[...arguments];
    // 先判断 是否是 push shift splice ，如果是的话，先取出插入的新值，后面进行 observeArray
    switch (methodsNeedChange[i]) {
      case 'push':
      case 'shift':
        inserted=args;
        break;
      case ' ':
        // splice(起始下标，删除个数，新添加的元素)
        inserted=args.slice(2);
    }
    // 先判断 inserted 里面有东西，才执行 observeArray
    inserted.length && observeArray(inserted);
    // 将备份的方法进行执行，毕竟不能丢失数组方法原本的功能执行
    original.apply(this,arguments)
    // 写监听到之后更新视图
  },false)
}

function defineReactive(obj,key,val) {
  let dep=new Dep();
  // eslint-disable-next-line no-unused-vars
  let childOb;
  // 判断当前入参个数，两个的话直接返回当前层的对象
  if(arguments.length===2){
    val=obj[key];
    childOb = observe(val)
  }
  Object.defineProperty(obj,key,{
    // 可枚举，默认为 false
    enumerable:true,
    // 属性的描述符能够被改变，或者是删除，默认为 false
    configurable:true,
    get(){
      // Dep.target 是我们弄的唯一标识，当有这个标识的时候，添加依赖
      if(Dep.target){
        dep.depend();
        // 如果有子属性，也要将它加入依赖
        if(childOb){
          childOb.dep.depend();
        }
      }
      return val;
    },
    set(newValue){
      val=newValue;
      childOb = observe(val)
      // notify 切忌 val=newValue 之后，不然在 callback 回调中一直是旧值
      dep.notify();
    }
  })
}

function def(obj,key,value,enumerable) {
  Object.defineProperty(obj,key,{
    value,
    //这个属性仅仅保存 Observer 实例，所以不需要遍历
    enumerable
  })
}

// 遍历对象当前层的所有属性，并且绑定 defineReactive
class Observer{
  constructor(obj){
    this.dep=new Dep();
    def(obj,'__ob__',this,false)
    if (Array.isArray(obj)){
      // 遍历当前数组，给所有的元素绑定 observe 响应式
      observeArray(obj)
      // 将当前数组对象的原型链强行指向 arrayMethods
      Object.setPrototypeOf(obj,arrayMethods);
    }else{
      this.walk(obj);
    }
  }
  // 遍历对象的当前层的所有属性， 给他绑定 defineReactive 响应式
  walk(obj){
    let keys=Object.keys(obj);
    for(let i =0;i<keys.length;i++){
      defineReactive(obj,keys[i])
    }
  }
}

// 响应式的入口方法 ，主要用于先判断是否是对象 ，然后判断是否有 __ob__ 属性，没有的话，肯定没有 Observer 遍历过
function observe(value) {
  // 判断传入的值是否是对象，不是对象直接返回，不进行后面的操作
  if(typeof value !== 'object') return;
  // 用来存储当前的 Observer 实例
  let ob;
  // 判定当前属性是否有 __ob__ ，并且该属性是否原型属于 Observer
  // eslint-disable-next-line no-prototype-builtins
  if(value.hasOwnProperty('__ob__') && value.__ob__ instanceof Observer){
    ob=value.__ob__;
  }else{
    // 没有 __ob__ 属性代表没有遍历过，先执行 new Observer(value)
    ob = new Observer(value);
  }
  return ob;
}

// 遍历数组，将他们 observe 进行响应式
function observeArray(list) {
  for(let i=0,l=list.length;i<l;i++){
    observe(list[i])
  }
}


class Dep{
  constructor() {
    // 用数组存储自己的订阅者   subs 是 subscribes 订阅者的意思
    // 这个数组里放的是 Watcher 的实例
    this.subs=[]
  }
  // 添加依赖
  depend(){
    // 判断当前是否有需要监听的目标,Dep.target 会被 Wacher 赋值
    if(Dep.target){
      // 将 Watcher 实例添加进 subs
      this.subs.push(Dep.target)
    }
  }
  // 通知所有订阅者
  notify(){
    // 浅克隆一份
    const subs=this.subs.slice();
    // 遍历
    for(let i=0,l=subs.length;i<l;i++){
      // 逐个更新
      subs[i].update();
    }
  }
}

class Watcher{
  // target 目标对象
  // expression 属性名
  // callback 回调函数
  // value 属性的值
  constructor(target,expression,callback) {
    this.target=target;
    // parsePath 为一个高阶函数
    this.getter=parsePath(expression);
    this.callback=callback;
    // get为我们之后要写的获取值的方法
    this.value=this.get();
  }
  // Dep发过来的通知，当前变量更新了，我们返回一个更新之后的回调函数
  update(){
    // this.value 由于还没触发更新，所以此时是旧的值
    const oldValue=this.value;
    // 通过我们的 getter 方法，直接获取最新的值
    const newValue=this.get();
    // 将新值和旧值返回给 callback 回调函数
    this.callback(newValue,oldValue);
  }
  // 获取当前的值，并将它更新，然后 return 返回
  get(){
    // 进入依赖收集阶段，将 Dep.target 设为 Watcher 实例本身
    Dep.target=this;

    // 当前对象
    const obj=this.target;
    let value;
    // 当对象不再使用的时候，我们需要将它清空
    try{
      value=this.getter(obj)
    }finally {
      Dep.target=null;
    }
    this.value=value
    return value;
  }
}

function parsePath(str){
  let segments = str.split('.');
  return obj=> {
      for(let i=0;i<segments.length;i++){
        if(!obj) return;
        obj=obj[segments[i]];
      }
      return obj;
  }
}

let a={
  b:{
    c:{
      d:10
    }
  }
}

observe(a)
new Watcher(a,'b.c.d',(val,oldValue)=>{
  console.log('ok',val,oldValue);  // ok 10 5
})
a.b.c.d=55;